module lpuart_basic_async_fifo #(
  parameter DATA_WIDTH = 16,
  parameter ADDR_WIDTH = 4
)
(
  input                   wclk,
  input                   wrst_n,
  input                   winc,
  input [DATA_WIDTH-1:0]  wdata,
  output                  wempty,
  output                  wfull,
  output                  wone_eighth_thr,
  output                  wone_quarter_thr,
  output                  wone_half_thr,
  output                  wthree_quarter_thr,
  output                  wseven_eighth_thr,

  input                   rclk,
  input                   rrst_n,
  input                   rinc,
  output [DATA_WIDTH-1:0] rdata,
  output                  rempty,
  output                  rfull,
  output                  rone_eighth_thr,
  output                  rone_quarter_thr,
  output                  rone_half_thr,
  output                  rthree_quarter_thr,
  output                  rseven_eighth_thr
);


wire [ADDR_WIDTH-1:0] waddr;
wire [ADDR_WIDTH-1:0] raddr;
wire [ADDR_WIDTH:0]   wptr, rptr, wq2_rptr, rq2_wptr;


sync_r2w u0_sync_r2w(
  .wq2_rptr (wq2_rptr),
  .rptr     (rptr),
  .wclk     (wclk),
  .wrst_n   (wrst_n)
);
sync_w2r u0_sync_w2r(
  .rq2_wptr (rq2_wptr),
  .wptr     (wptr),
  .rclk     (rclk),
  .rrst_n   (rrst_n)
);

fifo_mem #(.DATA_WIDTH(ADDR_WIDTH), .ADDR_WIDTH(ADDR_WIDTH))
u0_fifo_mem(
  .rdata   (rdata),
  .wdata   (wdata),
  .raddr   (raddr),
  .waddr   (waddr),
  .wclken  (winc),
  .wfull   (wfull),
  .wclk    (wclk)
);

rptr_status #(.ADDR_WIDTH(ADDR_WIDTH))
u0_rptr_status(
  .rempty               (rempty),
  .rfull                (rfull),
  .rone_eighth_thr      (rone_eighth_thr),
  .rone_quarter_thr     (rone_quarter_thr),
  .rone_half_thr        (rone_half_thr),
  .rthree_quarter_thr   (rthree_quarter_thr),
  .rseven_eighth_thr    (rseven_eighth_thr),
  .raddr                (raddr),
  .rptr                 (rptr),
  .rq2_wptr             (rq2_wptr),
  .rinc                 (rinc),
  .rclk                 (rclk),
  .rrst_n               (rrst_n)
);

wptr_status #(.ADDR_WIDTH(ADDR_WIDTH))
u0_wptr_status(
  .wempty             (wempty),
  .wfull              (wfull),
  .wone_eighth_thr    (wone_eighth_thr),
  .wone_quarter_thr   (wone_quarter_thr),
  .wone_half_thr      (wone_half_thr),
  .wthree_quarter_thr (wthree_quarter_thr),
  .wseven_eighth_thr  (wseven_eighth_thr),
  .waddr              (waddr),
  .wptr               (wptr),
  .wq2_rptr           (wq2_rptr),
  .winc               (winc),
  .wclk               (wclk),
  .wrst_n             (wrst_n)
);

endmodule


module fifo_mem #(
  parameter DATA_WIDTH = 16,
  parameter ADDR_WIDTH = 4
)
(
  output [DATA_WIDTH-1:0]  rdata,
  input  [DATA_WIDTH-1:0]  wdata,
  input  [ADDR_WIDTH-1:0]  waddr,
  input  [ADDR_WIDTH-1:0]  raddr,
  input  wclken,
  input  wfull,
  input  wclk
);

`ifdef ASYNC_FIFO_RAM
  ram_demo u0_ram_demo(
  );
`else
  localparam DEPTH = 1<<ADDR_WIDTH;

  reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

  assign rdata = mem[raddr];

  always @(posedge wclk) begin
    if(wclken && ~wfull)
      mem[waddr] = wdata;
  end
`endif

endmodule


module sync_r2w #(
  parameter ADDR_WIDTH = 4
)
(
  output reg [ADDR_WIDTH:0] wq2_rptr,
  input      [ADDR_WIDTH:0] rptr,
  input                     wclk,
  input                     wrst_n
);

reg [ADDR_WIDTH:0] wq1_rptr;

always@(posedge wclk, negedge wrst_n) begin
  if(!wrst_n) begin
    wq1_rptr <= 0;
    wq2_rptr <= 0;
  end else begin
    wq1_rptr <= rptr;
    wq2_rptr <= wq1_rptr;
  end
end

endmodule


module sync_w2r #(
  parameter ADDR_WIDTH = 4
)
(
  output reg [ADDR_WIDTH:0] rq2_wptr,
  input      [ADDR_WIDTH:0] wptr,
  input                     rclk,
  input                     rrst_n
);

reg [ADDR_WIDTH:0] rq1_wptr;

always@(posedge rclk, negedge rrst_n) begin
  if(!rrst_n) begin
    rq1_wptr <= 0;
    rq2_wptr <= 0;
  end else begin
    rq1_wptr <= wptr;
    rq2_wptr <= rq1_wptr;
  end
end

endmodule

module rptr_status #(
  parameter ADDR_WIDTH = 4
)
(
  output reg                  rempty,
  output reg                  rfull,
  output reg                  rone_eighth_thr,
  output reg                  rone_quarter_thr,
  output reg                  rone_half_thr,
  output reg                  rthree_quarter_thr,
  output reg                  rseven_eighth_thr,
  output     [ADDR_WIDTH-1:0] raddr,
  output reg [ADDR_WIDTH  :0] rptr,
  input      [ADDR_WIDTH  :0] rq2_wptr,
  input                       rinc,
  input                       rclk,
  input                       rrst_n
);

parameter FIFO_DEPTH           = (1<<ADDR_WIDTH);
parameter ONE_EIGHTH_LEVEL     = FIFO_DEPTH/8;
parameter ONE_QUARTER_LEVEL    = FIFO_DEPTH/4;
parameter ONE_HALF_LEVEL       = FIFO_DEPTH/2;
parameter THREE_QUARTERS_LEVEL = FIFO_DEPTH*3/4;
parameter SEVEN_EIGHTH_LEVEL   = FIFO_DEPTH*7/8;

reg  [ADDR_WIDTH:0] rbin;
wire [ADDR_WIDTH:0] rgray_next, rbin_next;

always @ (posedge rclk, negedge rrst_n) begin
  if(!rrst_n) begin
    rbin <= 'h0;
    rptr <= 'h0;
  end else begin
    rbin <= rbin_next;
    rptr <= rgray_next;
  end
end

assign raddr = rbin[ADDR_WIDTH-1:0];
assign rbin_next = rbin + (rinc & ~rempty);
assign rgray_next = func_bin2gray(rbin_next);

wire rempty_val = (rgray_next == rq2_wptr);
wire rfull_val  = (rgray_next == {~rq2_wptr[ADDR_WIDTH:ADDR_WIDTH-1], rq2_wptr[ADDR_WIDTH-2:0]});

wire [ADDR_WIDTH-1:0] rq2_wptr_bin;
wire [ADDR_WIDTH:0]   wrd_cnt;

assign rq2_wptr_bin = func_gray2bin(rq2_wptr);
assign wrd_cnt = rq2_wptr_bin - rbin_next;

wire rone_eighth_val    = (wrd_cnt >= ONE_EIGHTH_LEVEL);
wire rone_quarter_val   = (wrd_cnt >= ONE_QUARTER_LEVEL);
wire rone_half_val      = (wrd_cnt >= ONE_HALF_LEVEL);
wire rthree_quarter_val = (wrd_cnt >= THREE_QUARTERS_LEVEL);
wire rseven_eighth_val  = (wrd_cnt >= SEVEN_EIGHTH_LEVEL);

always @ (posedge rclk, negedge rrst_n) begin
  if(!rrst_n) begin
    rempty             <= 1'b1;
    rfull              <= 1'b1;
    rone_eighth_thr    <= 1'b0;
    rone_quarter_thr   <= 1'b0;
    rone_half_thr      <= 1'b0;
    rthree_quarter_thr <= 1'b0;
    rseven_eighth_thr  <= 1'b0;
  end else begin
    rempty             <= rempty_val;
    rfull              <= rfull_val;
    rone_eighth_thr    <= rone_eighth_val;
    rone_quarter_thr   <= rone_quarter_val;
    rone_half_thr      <= rone_half_val;
    rthree_quarter_thr <= rthree_quarter_val;
    rseven_eighth_thr  <= rseven_eighth_val;
  end
end


function [ADDR_WIDTH-1:0] func_bin2gray;
  input [ADDR_WIDTH-1:0] bin_num;
  begin
    func_bin2gray = bin_num ^ (bin_num >> 1);
  end
endfunction

function [ADDR_WIDTH-1:0] func_gray2bin;
  input [ADDR_WIDTH-1:0] gray_num;
  reg   [ADDR_WIDTH-1:0] bin_num;
  integer i;

  begin
    bin_num = {ADDR_WIDTH{1'b0}};
    for(i = ADDR_WIDTH-1; i>=0; i=i-1) begin
      if(i < ADDR_WIDTH-1)
        bin_num[i] = gray_num[i] ^ bin_num[i+1];
      else
        bin_num[i] = gray_num[i];
    end
    func_gray2bin = bin_num;
  end
endfunction

endmodule


module wptr_status #(
  parameter ADDR_WIDTH = 4
)
(
  output reg                  wempty,
  output reg                  wfull,
  output reg                  wone_eighth_thr,
  output reg                  wone_quarter_thr,
  output reg                  wone_half_thr,
  output reg                  wthree_quarter_thr,
  output reg                  wseven_eighth_thr,
  output     [ADDR_WIDTH-1:0] waddr,
  output reg [ADDR_WIDTH  :0] wptr,
  input      [ADDR_WIDTH  :0] wq2_rptr,
  input                       winc,
  input                       wclk,
  input                       wrst_n
);

parameter FIFO_DEPTH           = (1<<ADDR_WIDTH);
parameter ONE_EIGHTH_LEVEL     = FIFO_DEPTH/8;
parameter ONE_QUARTER_LEVEL    = FIFO_DEPTH/4;
parameter ONE_HALF_LEVEL       = FIFO_DEPTH/2;
parameter THREE_QUARTERS_LEVEL = FIFO_DEPTH*3/4;
parameter SEVEN_EIGHTH_LEVEL   = FIFO_DEPTH*7/8;

reg  [ADDR_WIDTH:0] wbin;
wire [ADDR_WIDTH:0] wgray_next, wbin_next;

always @ (posedge wclk, negedge wrst_n) begin
  if(!wrst_n) begin
    wbin <= 'h0;
    wptr <= 'h0;
  end else begin
    wbin <= wbin_next;
    wptr <= wgray_next;
  end
end

assign waddr = wbin[ADDR_WIDTH-1:0];
assign wbin_next = wbin + (winc & ~wfull);
assign wgray_next = func_bin2gray(wbin_next);

wire wfull_val = (wgray_next == {~wq2_rptr[ADDR_WIDTH:ADDR_WIDTH-1], wq2_rptr[ADDR_WIDTH-2:0]});
wire wempty_val         = (wgray_next == wq2_rptr);

wire [ADDR_WIDTH-1:0] wq2_rptr_bin;
wire [ADDR_WIDTH:0]   wrd_cnt;

assign wq2_rptr_bin = func_gray2bin(wq2_rptr);
assign wrd_cnt = wbin_next - wq2_rptr_bin;

wire wone_eighth_val    = (wrd_cnt >= ONE_EIGHTH_LEVEL);
wire wone_quarter_val   = (wrd_cnt >= ONE_QUARTER_LEVEL);
wire wone_half_val      = (wrd_cnt >= ONE_HALF_LEVEL);
wire wthree_quarter_val = (wrd_cnt >= THREE_QUARTERS_LEVEL);
wire wseven_eighth_val  = (wrd_cnt >= SEVEN_EIGHTH_LEVEL);

always @ (posedge wclk, negedge wrst_n) begin
  if(!wrst_n) begin
    wempty             <= 1'b1;
    wfull              <= 1'b0;
    wone_eighth_thr    <= 1'b0;
    wone_quarter_thr   <= 1'b0;
    wone_half_thr      <= 1'b0;
    wthree_quarter_thr <= 1'b0;
    wseven_eighth_thr  <= 1'b0;
  end else begin
    wempty             <= wempty_val;
    wfull              <= wfull_val;
    wone_eighth_thr    <= wone_eighth_val;
    wone_quarter_thr   <= wone_quarter_val;
    wone_half_thr      <= wone_half_val;
    wthree_quarter_thr <= wthree_quarter_val;
    wseven_eighth_thr  <= wseven_eighth_val;
  end
end


function [ADDR_WIDTH-1:0] func_bin2gray;
  input [ADDR_WIDTH-1:0] bin_num;
  begin
    func_bin2gray = bin_num ^ (bin_num >> 1);
  end
endfunction

function [ADDR_WIDTH-1:0] func_gray2bin;
  input [ADDR_WIDTH-1:0] gray_num;
  reg   [ADDR_WIDTH-1:0] bin_num;
  integer i;

  begin
    bin_num = {ADDR_WIDTH{1'b0}};
    for(i = ADDR_WIDTH-1; i>=0; i=i-1) begin
      if(i < ADDR_WIDTH-1)
        bin_num[i] = gray_num[i] ^ bin_num[i+1];
      else
        bin_num[i] = gray_num[i];
    end
    func_gray2bin = bin_num;
  end
endfunction

endmodule

